/*
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *  http://www.gnu.org/copyleft/gpl.html
 *
 *  TODO: do something about bidi (at least handle these ranges: 0590-07BF, FB1D-FDFF, FE70-FEFF)
 *
 */

#include "stdafx.h"
#include "Renderer.h"
#include "Arabic.h"

namespace ssf
{
template <class T>
void ReverseList(T& l)
{
    POSITION pos = l.GetHeadPosition();
    while(pos)
    {
        POSITION cur = pos;
        l.GetNext(pos);
        l.AddHead(l.GetAt(cur));
        l.RemoveAt(cur);
    }
}

static CPoint GetAlignPoint(const Placement& placement, const Size& scale, const CRect& frame, const CSize& size)
{
    CPoint p;

    p.x = frame.left;
    p.x += placement.pos.auto_x
           ? placement.align.h * (frame.Width() - size.cx)
           : placement.pos.x * scale.cx - placement.align.h * size.cx;

    p.y = frame.top;
    p.y += placement.pos.auto_y
           ? placement.align.v * (frame.Height() - size.cy)
           : placement.pos.y * scale.cy - placement.align.v * size.cy;

    return p;
}

static CPoint GetAlignPoint(const Placement& placement, const Size& scale, const CRect& frame)
{
    CSize size(0, 0);
    return GetAlignPoint(placement, scale, frame, size);
}

//

Renderer::Renderer()
{
    m_hDC = CreateCompatibleDC(NULL);
    SetBkMode(m_hDC, TRANSPARENT);
    SetTextColor(m_hDC, 0xffffff);
    SetMapMode(m_hDC, MM_TEXT);
}

Renderer::~Renderer()
{
    DeleteDC(m_hDC);
}

void Renderer::NextSegment(const CAutoPtrList<Subtitle>& subs)
{
    StringMapW<bool> names;
    POSITION pos = subs.GetHeadPosition();
    while(pos) names[subs.GetNext(pos)->m_name] = true;

    pos = m_sra.GetStartPosition();
    while(pos)
    {
        POSITION cur = pos;
        const CStringW& name = m_sra.GetNextKey(pos);
        if(!names.Lookup(name)) m_sra.RemoveAtPos(cur);
    }
}

RenderedSubtitle* Renderer::Lookup(const Subtitle* s, const CSize& vs, const CRect& vr)
{
    m_sra.UpdateTarget(vs, vr);

    if(s->m_text.IsEmpty())
        return NULL;

    CRect spdrc = s->m_frame.reference == _T("video") ? vr : CRect(CPoint(0, 0), vs);

    if(spdrc.IsRectEmpty())
        return NULL;

    RenderedSubtitle* rs = NULL;

    if(m_rsc.Lookup(s->m_name, rs))
    {
        if(!s->m_animated && rs->m_spdrc == spdrc)
            return rs;

        m_rsc.Invalidate(s->m_name);
    }

    const Style& style = s->m_text.GetHead().style;

    Size scale;

    scale.cx = (float)spdrc.Width() / s->m_frame.resolution.cx;
    scale.cy = (float)spdrc.Height() / s->m_frame.resolution.cy;

    CRect frame;

    frame.left = (int)(64.0f * (spdrc.left + style.placement.margin.l * scale.cx) + 0.5);
    frame.top = (int)(64.0f * (spdrc.top + style.placement.margin.t * scale.cy) + 0.5);
    frame.right = (int)(64.0f * (spdrc.right - style.placement.margin.r * scale.cx) + 0.5);
    frame.bottom = (int)(64.0f * (spdrc.bottom - style.placement.margin.b * scale.cy) + 0.5);

    CRect clip;

    if(style.placement.clip.l == -1) clip.left = 0;
    else clip.left = (int)(spdrc.left + style.placement.clip.l * scale.cx);
    if(style.placement.clip.t == -1) clip.top = 0;
    else clip.top = (int)(spdrc.top + style.placement.clip.t * scale.cy);
    if(style.placement.clip.r == -1) clip.right = vs.cx;
    else clip.right = (int)(spdrc.left + style.placement.clip.r * scale.cx);
    if(style.placement.clip.b == -1) clip.bottom = vs.cy;
    else clip.bottom = (int)(spdrc.top + style.placement.clip.b * scale.cy);

    clip.left = max(clip.left, 0);
    clip.top = max(clip.top, 0);
    clip.right = min(clip.right, vs.cx);
    clip.bottom = min(clip.bottom, vs.cy);

    scale.cx *= 64;
    scale.cy *= 64;

    bool vertical = s->m_direction.primary == _T("down") || s->m_direction.primary == _T("up");

    // create glyph paths

    WCHAR c_prev = 0, c_next;

    CAutoPtrList<Glyph> glyphs;

    POSITION pos = s->m_text.GetHeadPosition();
    while(pos)
    {
        const Text& t = s->m_text.GetNext(pos);

        LOGFONT lf;
        memset(&lf, 0, sizeof(lf));
        lf.lfCharSet = DEFAULT_CHARSET;
        _tcscpy_s(lf.lfFaceName, CString(t.style.font.face));
        lf.lfHeight = (LONG)(t.style.font.size * scale.cy + 0.5);
        lf.lfWeight = (LONG)(t.style.font.weight + 0.5);
        lf.lfItalic = !!t.style.font.italic;
        lf.lfUnderline = !!t.style.font.underline;
        lf.lfStrikeOut = !!t.style.font.strikethrough;
        lf.lfOutPrecision = OUT_TT_PRECIS;
        lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
        lf.lfQuality = ANTIALIASED_QUALITY;
        lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;

        FontWrapper* font = m_fc.Create(m_hDC, lf); 
 	
        if(!font) 
        {
            _tcscpy_s(lf.lfFaceName, _T("Arial"));

            font = m_fc.Create(m_hDC, lf);
            if(!font)
            {
                ASSERT(0);
                continue;
            }
        }

        HFONT hOldFont = SelectFont(m_hDC, *font);

        const TEXTMETRIC& tm = font->GetTextMetric();

        for(LPCWSTR c = t.str; *c; c++)
        {
            CAutoPtr<Glyph> g(DNew Glyph());

            g->c = *c;
            g->style = t.style;
            g->scale = scale;
            g->vertical = vertical;
            g->font = font;

            c_next = !c[1] && pos ? c_next = s->m_text.GetAt(pos).str[0] : c[1];
            Arabic::Replace(g->c, c_prev, c_next);
            c_prev = c[0];

            CSize extent;
            GetTextExtentPoint32W(m_hDC, &g->c, 1, &extent);
            ASSERT(extent.cx >= 0 && extent.cy >= 0);

            if(vertical)
            {
                g->spacing = (int)(t.style.font.spacing * scale.cy + 0.5);
                g->ascent = extent.cx / 2;
                g->descent = extent.cx - g->ascent;
                g->width = extent.cy;

                // TESTME
                if(g->c == Text::SP)
                {
                    g->width /= 2;
                }
            }
            else
            {
                g->spacing = (int)(t.style.font.spacing * scale.cx + 0.5);
                g->ascent = tm.tmAscent;
                g->descent = tm.tmDescent;
                g->width = extent.cx;
            }

            if(g->c == Text::LSEP)
            {
                g->spacing = 0;
                g->width = 0;
                g->ascent /= 2;
                g->descent /= 2;
            }
            else
            {
                GlyphPath* path = m_gpc.Create(m_hDC, font, g->c);
                if(!path)
                {
                    ASSERT(0);
                    continue;
                }
                g->path = *path;
            }

            glyphs.AddTail(g);
        }

        SelectFont(m_hDC, hOldFont);
    }

    // break glyphs into rows

    CAutoPtrList<Row> rows;
    CAutoPtr<Row> row;

    pos = glyphs.GetHeadPosition();
    while(pos)
    {
        CAutoPtr<Glyph> g = glyphs.GetNext(pos);
        if(!row) row.Attach(DNew Row());
        WCHAR c = g->c;
        row->AddTail(g);
        if(c == Text::LSEP || !pos) rows.AddTail(row);
    }

    // kerning

    if(s->m_direction.primary == _T("right")) // || s->m_direction.primary == _T("left")
    {
        for(POSITION rpos = rows.GetHeadPosition(); rpos; rows.GetNext(rpos))
        {
            Row* r = rows.GetAt(rpos);

            POSITION gpos = r->GetHeadPosition();
            while(gpos)
            {
                Glyph* g1 = r->GetNext(gpos);
                if(!gpos) break;

                Glyph* g2 = r->GetAt(gpos);
                if(g1->font != g2->font || !g1->style.font.kerning || !g2->style.font.kerning)
                    continue;

                if(int size = g1->font->GetKernAmount(g1->c, g2->c))
                {
                    g2->path.MovePoints(CPoint(size, 0));
                    g2->width += size;
                }
            }
        }
    }

    // wrap rows

    if(s->m_wrap == _T("normal") || s->m_wrap == _T("even"))
    {
        int maxwidth = abs((int)(vertical ? frame.Height() : frame.Width()));
        int minwidth = 0;

        for(POSITION rpos = rows.GetHeadPosition(); rpos; rows.GetNext(rpos))
        {
            Row* r = rows.GetAt(rpos);

            POSITION brpos = NULL;

            if(s->m_wrap == _T("even"))
            {
                int fullwidth = 0;

                for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
                {
                    const Glyph* g = r->GetAt(gpos);

                    fullwidth += g->width + g->spacing;
                }

                fullwidth = abs(fullwidth);

                if(fullwidth > maxwidth)
                {
                    maxwidth = fullwidth / ((fullwidth / maxwidth) + 1);
                    minwidth = maxwidth;
                }
            }

            int width = 0;

            for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
            {
                const Glyph* g = r->GetAt(gpos);

                width += g->width + g->spacing;

                if(brpos && abs(width) > maxwidth && g->c != Text::SP)
                {
                    row.Attach(DNew Row());
                    POSITION next = brpos;
                    r->GetNext(next);
                    do
                    {
                        row->AddHead(r->GetPrev(brpos));
                    }
                    while(brpos);
                    rows.InsertBefore(rpos, row);
                    while(!r->IsEmpty() && r->GetHeadPosition() != next) r->RemoveHeadNoReturn();
                    g = r->GetAt(gpos = next);
                    width = g->width + g->spacing;
                }

                if(abs(width) >= minwidth)
                {
                    if(g->style.linebreak == _T("char")
                       || g->style.linebreak == _T("word") && g->c == Text::SP)
                    {
                        brpos = gpos;
                    }
                }
            }
        }
    }

    // trim rows

    for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
    {
        Row* r = rows.GetAt(pos);

        while(!r->IsEmpty() && r->GetHead()->c == Text::SP)
            r->RemoveHead();

        while(!r->IsEmpty() && r->GetTail()->c == Text::SP)
            r->RemoveTail();
    }

    // calc fill width for each glyph

    CAtlList<Glyph*> glypsh2fill;
    int fill_id = 0;
    int fill_width = 0;

    for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
    {
        Row* r = rows.GetAt(pos);

        POSITION gpos = r->GetHeadPosition();
        while(gpos)
        {
            Glyph* g = r->GetNext(gpos);

            if(!glypsh2fill.IsEmpty() && fill_id && (g->style.fill.id != fill_id || !pos && !gpos))
            {
                int w = (int)(g->style.fill.width * fill_width + 0.5);

                while(!glypsh2fill.IsEmpty())
                {
                    Glyph* g = glypsh2fill.RemoveTail();
                    fill_width -= g->width;
                    g->fill = w - fill_width;
                }

                ASSERT(glypsh2fill.IsEmpty());
                ASSERT(fill_width == 0);

                glypsh2fill.RemoveAll();
                fill_width = 0;
            }

            fill_id = g->style.fill.id;

            if(g->style.fill.id)
            {
                glypsh2fill.AddTail(g);
                fill_width += g->width;
            }
        }
    }

    // calc row sizes and total subtitle size

    CSize size(0, 0);

    if(s->m_direction.secondary == _T("left") || s->m_direction.secondary == _T("up"))
        ReverseList(rows);

    for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
    {
        Row* r = rows.GetAt(pos);

        if(s->m_direction.primary == _T("left") || s->m_direction.primary == _T("up"))
            ReverseList(*r);

        int w = 0, h = 0;

        r->width = 0;

        for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
        {
            const Glyph* g = r->GetAt(gpos);

            w += g->width;
            if(gpos) w += g->spacing;
            h = max(h, g->ascent + g->descent);

            r->width += g->width;
            if(gpos) r->width += g->spacing;
            r->ascent = max(r->ascent, g->ascent);
            r->descent = max(r->descent, g->descent);
            r->border = max(r->border, g->GetBackgroundSize());
        }

        for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
        {
            Glyph* g = r->GetAt(gpos);
            g->row_ascent = r->ascent;
            g->row_descent = r->descent;
        }

        if(vertical)
        {
            size.cx += h;
            size.cy = max(size.cy, w);
        }
        else
        {
            size.cx = max(size.cx, w);
            size.cy += h;
        }
    }

    // align rows and calc glyph positions

    rs = DNew RenderedSubtitle(spdrc, clip);

    CPoint p = GetAlignPoint(style.placement, scale, frame, size);
    CPoint org = GetAlignPoint(style.placement, scale, frame);

    // collision detection

    if(!s->m_animated)
    {
        int tlb = !rows.IsEmpty() ? rows.GetHead()->border : 0;
        int brb = !rows.IsEmpty() ? rows.GetTail()->border : 0;

        CRect r(p, size);
        m_sra.GetRect(r, s, style.placement.align, tlb, brb);
        org += r.TopLeft() - p;
        p = r.TopLeft();
    }

    CRect subrect(p, size);

    // continue positioning

    for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
    {
        Row* r = rows.GetAt(pos);

        CSize rsize;
        rsize.cx = rsize.cy = r->width;

        if(vertical)
        {
            p.y = GetAlignPoint(style.placement, scale, frame, rsize).y;

            for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
            {
                CAutoPtr<Glyph> g = r->GetAt(gpos);
                g->tl.x = p.x + (int)(g->style.placement.offset.x * scale.cx + 0.5) + r->ascent - g->ascent;
                g->tl.y = p.y + (int)(g->style.placement.offset.y * scale.cy + 0.5);
                p.y += g->width + g->spacing;
                rs->m_glyphs.AddTail(g);
            }

            p.x += r->ascent + r->descent;
        }
        else
        {
            p.x = GetAlignPoint(style.placement, scale, frame, rsize).x;

            for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
            {
                CAutoPtr<Glyph> g = r->GetAt(gpos);
                g->tl.x = p.x + (int)(g->style.placement.offset.x * scale.cx + 0.5);
                g->tl.y = p.y + (int)(g->style.placement.offset.y * scale.cy + 0.5) + r->ascent - g->ascent;
                p.x += g->width + g->spacing;
                rs->m_glyphs.AddTail(g);
            }

            p.y += r->ascent + r->descent;
        }
    }

    // bkg, precalc style.placement.path, transform

    pos = rs->m_glyphs.GetHeadPosition();
    while(pos)
    {
        Glyph* g = rs->m_glyphs.GetNext(pos);
        g->CreateBkg();
        g->CreateSplineCoeffs(spdrc);
        g->Transform(org, subrect);
    }

    // merge glyphs (TODO: merge 'fill' too)

    Glyph* g0 = NULL;

    pos = rs->m_glyphs.GetHeadPosition();
    while(pos)
    {
        POSITION cur = pos;

        Glyph* g = rs->m_glyphs.GetNext(pos);

        CRect r = g->bbox + g->tl;

        int size = (int)(g->GetBackgroundSize() + 0.5);
        int depth = (int)(g->GetShadowDepth() + 0.5);

        r.InflateRect(size, size);
        r.InflateRect(depth, depth);

        r.left >>= 6;
        r.top >>= 6;
        r.right = (r.right + 32) >> 6;
        r.bottom = (r.bottom + 32) >> 6;

        if((r & clip).IsRectEmpty()) // clip
        {
            rs->m_glyphs.RemoveAt(cur);
        }
        else if(g0 && g0->style.IsSimilar(g->style)) // append
        {
            CPoint o = g->tl - g0->tl;

            g->path.MovePoints(o);

            g0->path.types.Append(g->path.types);
            g0->path.points.Append(g->path.points);

            g->path_bkg.MovePoints(o);

            g0->path_bkg.types.Append(g->path_bkg.types);
            g0->path_bkg.points.Append(g->path_bkg.points);

            g0->bbox |= g->bbox + o;

            rs->m_glyphs.RemoveAt(cur);
        }
        else // leave alone
        {
            g0 = g;
        }
    }

    // rasterize

    pos = rs->m_glyphs.GetHeadPosition();
    while(pos) rs->m_glyphs.GetNext(pos)->Rasterize();

    // cache

    m_rsc.Add(s->m_name, rs);

    m_fc.Flush();

    return rs;
}

//

CRect RenderedSubtitle::Draw(SubPicDesc& spd) const
{
    CRect bbox;
    bbox.SetRectEmpty();

    // shadow

    POSITION pos = m_glyphs.GetHeadPosition();
    while(pos)
    {
        Glyph* g = m_glyphs.GetNext(pos);

        if(g->style.shadow.depth <= 0) continue;

        DWORD c = g->style.shadow.color;
        DWORD sw[6] = {c, (DWORD)-1};

        bool outline = g->style.background.type == L"outline" && g->style.background.size > 0;

        bbox |= g->ras_shadow.Draw(spd, m_clip, g->tls.x, g->tls.y, sw, outline ? 1 : 0);
    }

    // background

    pos = m_glyphs.GetHeadPosition();
    while(pos)
    {
        Glyph* g = m_glyphs.GetNext(pos);

        DWORD c = g->style.background.color;
        DWORD sw[6] = {c, (DWORD)-1};

        if(g->style.background.type == L"outline" && g->style.background.size > 0)
        {
            bbox |= g->ras.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, g->style.font.color.a < 255 ? 2 : 1);
        }
        else if(g->style.background.type == L"enlarge" && g->style.background.size > 0
                || g->style.background.type == L"box" && g->style.background.size >= 0)
        {
            bbox |= g->ras_bkg.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, 0);
        }
    }

    // body

    pos = m_glyphs.GetHeadPosition();
    while(pos)
    {
        Glyph* g = m_glyphs.GetNext(pos);

        DWORD c = g->style.font.color;
        DWORD sw[6] = {c, (DWORD)-1}; // TODO: fill

        bbox |= g->ras.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, 0);
    }

    return bbox;
}

//

void SubRectAllocator::UpdateTarget(const CSize& s, const CRect& r)
{
    if(vs != s || vr != r) RemoveAll();
    vs = s;
    vr = r;
}

void SubRectAllocator::GetRect(CRect& rect, const Subtitle* s, const Align& align, int tlb, int brb)
{
    SubRect sr(rect, s->m_layer);
    sr.rect.InflateRect(tlb, tlb, brb, brb);

    StringMapW<SubRect>::CPair* pPair = Lookup(s->m_name);

    if(pPair && pPair->m_value.rect != sr.rect)
    {
        RemoveKey(s->m_name);
        pPair = NULL;
    }

    if(!pPair)
    {
        bool vertical = s->m_direction.primary == _T("down") || s->m_direction.primary == _T("up");

        bool fOK = false;

        while(!fOK)
        {
            fOK = true;

            POSITION pos = GetStartPosition();
            while(pos)
            {
                const SubRect& sr2 = GetNextValue(pos);

                if(sr.layer == sr2.layer && !(sr.rect & sr2.rect).IsRectEmpty())
                {
                    if(vertical)
                    {
                        if(align.h < 0.5)
                        {
                            sr.rect.right = sr2.rect.right + sr.rect.Width();
                            sr.rect.left = sr2.rect.right;
                        }
                        else
                        {
                            sr.rect.left = sr2.rect.left - sr.rect.Width();
                            sr.rect.right = sr2.rect.left;
                        }
                    }
                    else
                    {
                        if(align.v < 0.5)
                        {
                            sr.rect.bottom = sr2.rect.bottom + sr.rect.Height();
                            sr.rect.top = sr2.rect.bottom;
                        }
                        else
                        {
                            sr.rect.top = sr2.rect.top - sr.rect.Height();
                            sr.rect.bottom = sr2.rect.top;
                        }
                    }

                    fOK = false;
                }
            }
        }

        SetAt(s->m_name, sr);

        rect = sr.rect;
        rect.DeflateRect(tlb, tlb, brb, brb);
    }
}

//

FontWrapper* FontCache::Create(HDC hDC, const LOGFONT& lf)
{
    CStringW key;

    key.Format(L"%s,%d,%d,%d",
               CStringW(lf.lfFaceName), lf.lfHeight, lf.lfWeight,
               ((lf.lfItalic & 1) << 2) | ((lf.lfUnderline & 1) << 1) | ((lf.lfStrikeOut & 1) << 0));

    FontWrapper* pFW = NULL;

    if(m_key2obj.Lookup(key, pFW))
    {
        return pFW;
    }

    HFONT hFont = CreateFontIndirect(&lf);

    if(!hFont)
    {
        ASSERT(0);
        return NULL;
    }

    pFW = DNew FontWrapper(hDC, hFont, key);

    Add(key, pFW, false);

    return pFW;
}

//

GlyphPath* GlyphPathCache::Create(HDC hDC, const FontWrapper* f, WCHAR c)
{
    CStringW key = CStringW((LPCWSTR) * f) + c;

    GlyphPath* path = NULL;

    if(m_key2obj.Lookup(key, path))
    {
        return path;
    }

    BeginPath(hDC);
    TextOutW(hDC, 0, 0, &c, 1);
    CloseFigure(hDC);
    if(!EndPath(hDC))
    {
        AbortPath(hDC);
        ASSERT(0);
        return NULL;
    }

    path = DNew GlyphPath();

    int count = GetPath(hDC, NULL, NULL, 0);

    if(count > 0)
    {
        path->points.SetCount(count);
        path->types.SetCount(count);

        if(count != GetPath(hDC, path->points.GetData(), path->types.GetData(), count))
        {
            ASSERT(0);
            delete path;
            return NULL;
        }
    }

    Add(key, path);

    return path;
}
}